﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using System.Data.SqlTypes;

namespace dasBlog.Storage.SqlServer
{
    public interface ITextEntryStorageProvider : IStorageProvider<dasBlog.Storage.TextEntry> { }

    public class TextEntryStorageProvider : ITextEntryStorageProvider, IInitStorageProvider
    {
        private dasBlogStorageSqlDataContext readContext;
        private string connectionString;
        private bool initialized;

        public TextEntryStorageProvider()
        {
            
        }

        public void Initialize(string initData)
        {
            if (string.IsNullOrEmpty(initData))
            {
                throw new ArgumentNullException("initData");
            }
            else
            {
                connectionString = initData;
                // set the read context
                readContext = new dasBlogStorageSqlDataContext(initData);
                readContext.DeferredLoadingEnabled = false;
                readContext.ObjectTrackingEnabled = false;
                initialized = true;
            }
        }

        private void EnsureInit()
        {
            lock (this)
            {
                if (!initialized)
                {
                    Initialize(Properties.Settings.Default.dasBlogStorageSqlConnectionString);
                }
            }
        }

        public IEnumerable<dasBlog.Storage.TextEntry> Select(Moniker moniker, MonikerMatch contextMatch, QueryDescription query)
        {
            if (moniker == null) throw new ArgumentNullException("moniker");

            EnsureInit();
            if (query == null)
            {
                if (contextMatch == MonikerMatch.Exact)
                {
                    return from TextEntry t in readContext.TextEntries
                           where t.ContextKey == moniker.ItemPath
                           orderby t.Created descending
                           select ToStorageTextEntry(t);
                }
                else
                {
                    return from TextEntry t in readContext.TextEntries
                           where moniker.ToString() == t.ContextKey.Substring(0, moniker.ToString().Length)
                           orderby t.Created descending
                           select ToStorageTextEntry(t);
                }
            }
            else
            {
                var args = new Dictionary<string, string>();
                foreach (QueryArgument arg in query.Arguments)
                {
                    args.Add(arg.Name, arg.Value);
                }

                if (query.QueryName == IdOnlyQueryDescription.Name)
                {
                    if (contextMatch == MonikerMatch.Exact)
                    {
                        return from TextEntry t in readContext.TextEntries
                               where t.ContextKey == moniker.ItemPath
                               orderby t.Created descending
                               select new dasBlog.Storage.TextEntry
                               {
                                   Id = new Moniker(new Moniker(t.ContextKey), t.Id.ToString()),
                                   Title = t.Title,
                                   Created = t.Created
                               };
                    }
                    else
                    {
                        return from TextEntry t in readContext.TextEntries
                               where moniker.ToString() == t.ContextKey.Substring(0, moniker.ToString().Length)
                               orderby t.Created descending
                               select new dasBlog.Storage.TextEntry
                               {
                                   Id = new Moniker(new Moniker(t.ContextKey), t.Id.ToString()),
                                   Title = t.Title,
                                   Created = t.Created
                               };
                    }
                }
                else if (query.QueryName == DateRangeQueryDescription.Name)
                {
                    DateTime @from =
                        args[DateRangeQueryDescription.FromArgument] != null ?
                        DateTime.Parse(args[DateRangeQueryDescription.FromArgument]).ToSqlRange() :
                        DateTimeTools.SqlMin;
                    DateTime @to =
                        args[DateRangeQueryDescription.ToArgument] != null ?
                        DateTime.Parse(args[DateRangeQueryDescription.ToArgument]).ToSqlRange() :
                        DateTimeTools.SqlMax;
                    
                    if (contextMatch == MonikerMatch.Exact)
                    {
                        return from TextEntry t in readContext.TextEntries
                               where
                                  t.ContextKey == moniker.ItemPath &&
                                  t.Created < @to &&
                                  t.Created > @from
                               orderby t.Created descending
                               select ToStorageTextEntry(t);
                    }
                    else
                    {
                        return from TextEntry t in readContext.TextEntries
                               where
                                  moniker.ToString() == t.ContextKey.Substring(0, moniker.ToString().Length) &&
                                  t.Created < @to &&
                                  t.Created > @from
                               orderby t.Created descending
                               select ToStorageTextEntry(t);
                    }
                }
                else if (query.QueryName == TagQueryDescription.Name)
                {
                    if (contextMatch == MonikerMatch.Exact)
                    {
                        return from TaggedEntry te in readContext.TaggedEntries
                               from TextEntry tx in readContext.TextEntries
                               where
                                  tx.ContextKey == te.ContextKey &&
                                  tx.Id == te.Id &&
                                  tx.ContextKey == moniker.ItemPath &&
                                  te.Tag == (string)args[TagQueryDescription.TagArgument]
                               orderby te.TextEntry.Created descending
                               select ToStorageTextEntry(tx);
                    }
                    else
                    {
                        return from TaggedEntry te in readContext.TaggedEntries
                               from TextEntry tx in readContext.TextEntries
                               where
                                  tx.ContextKey == te.ContextKey &&
                                  tx.Id == te.Id &&
                                  moniker.ToString() == tx.ContextKey.Substring(0, moniker.ToString().Length) &&
                                  te.Tag == (string)args[TagQueryDescription.TagArgument]
                               orderby te.TextEntry.Created descending
                               select ToStorageTextEntry(tx);
                    }
                }
                else if (query.QueryName == ContentQueryDescription.Name)
                {
                    if (contextMatch == MonikerMatch.Exact)
                    {
                        return from TextEntry t in readContext.TextEntries
                               where
                                  t.ContextKey == moniker.ToString() &&
                                  t.Content.Contains((string)args[ContentQueryDescription.SnippetArgument])
                               orderby t.Created descending
                               select ToStorageTextEntry(t);
                    }
                    else
                    {
                        return from TextEntry t in readContext.TextEntries
                               where
                                  moniker.ToString() == t.ContextKey.Substring(0, moniker.ToString().Length) &&
                                  t.Content.Contains((string)args[ContentQueryDescription.SnippetArgument])
                               orderby t.Created descending
                               select ToStorageTextEntry(t);
                    }
                }
                else
                {
                    if ( query.QueryName == null )
                        throw new ArgumentException("query",
                                                    string.Format("Query name cannot be null"));
                    else
                        throw new ArgumentOutOfRangeException("query",
                                                    string.Format("Unsupported query '{0}'", query.QueryName));
                }
            }
        }

        public dasBlog.Storage.TextEntry Get(Moniker itemId)
        {
            if (itemId == null) throw new ArgumentNullException("itemId");

            EnsureInit();
            var result = from TextEntry t in readContext.TextEntries
                         where t.ContextKey == itemId.ItemPath &&
                               t.Id == itemId.ItemId
                         select ToStorageTextEntry(t);

            if (result == null || result.Count() == 0)
            {
                return null;
            }
            else
            {
                return result.First();
            }
        }

        public void Delete(Moniker itemId)
        {
            if (itemId == null) throw new ArgumentNullException("itemId");

            EnsureInit();
            using (var writeContext = new dasBlogStorageSqlDataContext(connectionString))
            {
                

                writeContext.TaggedEntries.RemoveAll<TaggedEntry>(from TaggedEntry t in writeContext.TaggedEntries
                                                                 where
                                                                  t.ContextKey == itemId.ItemPath &&
                                                                  t.Id == itemId.ItemId
                                                                 select t);
                writeContext.TextEntries.RemoveAll<TextEntry>(from TextEntry t in writeContext.TextEntries
                                                              where t.ContextKey == itemId.ItemPath &&
                                                                 t.Id == itemId.ItemId
                                                             select t);
                writeContext.SubmitChanges();
            }
        }

        public Moniker Store(Moniker moniker, dasBlog.Storage.TextEntry item)
        {
            Moniker result = null;

            if (moniker == null) throw new ArgumentNullException("contextId");
            if (item == null) throw new ArgumentNullException("item");

            EnsureInit();
            using (var writeContext = new dasBlogStorageSqlDataContext(connectionString))
            {
                if (moniker.ItemId != null)
                {
                    var existingEntry = writeContext.TextEntries.Single((textEntry) => textEntry.ContextKey == moniker.ItemPath &&
                                                                             textEntry.Id == moniker.ItemId);
                    if (existingEntry != null)
                    {
                        UpdateFromStorageTextEntry(existingEntry, item);
                        if (item.Categories.Count > 0)
                        {
                            UpdateTags(item, writeContext, existingEntry);
                        }
                        else
                        {
                            existingEntry.TaggedEntries.Clear();
                            existingEntry.Tags = "";
                        }
                        result = moniker;
                    }
                    else
                    {
                        var newEntry = FromStorageTextEntry(item);
                        newEntry.ContextKey = moniker.ItemPath;
                        newEntry.Id = Guid.NewGuid().ToString();
                        writeContext.TextEntries.Add(newEntry);
                        if (item.Categories.Count > 0)
                        {
                            UpdateTags(item, writeContext, newEntry);
                        }
                        else
                        {
                            newEntry.Tags = "";
                        }
                        result = moniker;
                    }

                }
                else
                {
                    var newEntry = FromStorageTextEntry(item);
                    newEntry.ContextKey = moniker.ItemPath;
                    newEntry.Id = Guid.NewGuid().ToString();

                    writeContext.TextEntries.Add(newEntry);
                    if (item.Categories.Count > 0)
                    {
                        UpdateTags(item, writeContext, newEntry);
                    }
                    else
                    {
                        newEntry.Tags = "";
                    }
                    result = new Moniker(moniker, newEntry.Id);
                }
                writeContext.SubmitChanges();
                return result;
            }
        }


        private static void UpdateTags(dasBlog.Storage.TextEntry item, dasBlogStorageSqlDataContext dc, TextEntry existingEntry)
        {
            existingEntry.TaggedEntries.AddRange(
                from string tag
                in item.Categories
                where existingEntry.TaggedEntries.Count((te) => tag == te.Tag) == 0
                select new TaggedEntry { Tag = tag }
            );

            IEnumerable<TaggedEntry> entriesToRemove = from TaggedEntry te in existingEntry.TaggedEntries
                                                       where !item.Categories.Contains(te.Tag)
                                                       select te;
            foreach (TaggedEntry te2 in entriesToRemove)
            {
                existingEntry.TaggedEntries.Remove(te2);

                dc.TaggedEntries.Remove(te2);
            }
            existingEntry.Tags = item.CategoriesAsString;
        }

        dasBlog.Storage.TextEntry ToStorageTextEntry(TextEntry t)
        {
            return new dasBlog.Storage.TextEntry
                            {
                                Content = t.Content,
                                ContentType = t.ContentType,
                                CategoriesAsString = t.Tags,
                                Created = t.Created.ToSqlRange(),
                                LastChange = t.LastUpdate.ToSqlRangeOrMin(),
                                Id = new Moniker(new Moniker(t.ContextKey), t.Id.ToString()),
                                Summary = t.Summary,
                                Title = t.Title,
                                Language = t.Languages,
                                AllowComments = t.AllowComments,
                                Featured = t.Featured,
                                Parent = t.Parent,
                                Published = t.Published,
                                Reference = t.Reference,
                                Syndicated = t.Syndicated,
                                Author = new AuthorDescription{ Url = t.AuthorUrl, Email = t.AuthorEmail, DisplayName = t.AuthorName }
                            };
        }

        TextEntry FromStorageTextEntry(dasBlog.Storage.TextEntry item)
        {
            Moniker uri = item.Id != null ? item.Id : null;
            return new TextEntry
            {
                Content = item.Content,
                ContentType = item.ContentType.Truncate(10),
                LastUpdate = item.Created,
                Created = item.Created,
                Published = item.Published,
                Languages = item.Language,
                Title = item.Title.Truncate(150),
                Summary = item.Summary.Truncate(300),
                AllowComments = item.AllowComments,
                Featured = item.Featured,
                Parent = item.Parent.Truncate(255),
                Reference = item.Reference,
                Syndicated = item.Syndicated,
                AuthorEmail = item.Author!=null?item.Author.Email:null,
                AuthorUrl = item.Author!=null?item.Author.Url:null,
                AuthorName = item.Author != null ? item.Author.DisplayName : null,                
                ContextKey = uri != null ? uri.ItemPath : null,
                Id = uri != null && uri.ItemId != null ? uri.ItemId : null
            };
        }

        void UpdateFromStorageTextEntry(TextEntry textEntry, dasBlog.Storage.TextEntry item)
        {
            textEntry.Content = item.Content;
            textEntry.ContentType = item.ContentType.Truncate(10);
            textEntry.LastUpdate = item.Created;
            textEntry.Created = item.Created;
            textEntry.Published = item.Published;
            textEntry.Languages = item.Language;
            textEntry.Title = item.Title.Truncate(150);
            textEntry.Summary = item.Summary.Truncate(300);
            textEntry.AllowComments = item.AllowComments;
            textEntry.Featured = item.Featured;
            textEntry.Parent = item.Parent.Truncate(255);
            textEntry.Reference = item.Reference;
            textEntry.Syndicated = item.Syndicated;
            textEntry.AuthorEmail = item.Author!=null?item.Author.Email:null;
            textEntry.AuthorUrl = item.Author!=null?item.Author.Url:null;
            textEntry.AuthorName = item.Author != null ? item.Author.DisplayName : null;                
        }


        public IEnumerable<PropertyAggregate> Aggregate(Moniker contextId, MonikerMatch contextMatch, string propertyName)
        {
            if ( propertyName == null ) throw new ArgumentNullException("propertyName");
            if ( contextId == null ) throw new ArgumentNullException("contextId");

            EnsureInit();
            if (string.Compare(propertyName, "tag", true) == 0)
            {
                if (contextMatch == MonikerMatch.Exact)
                {
                    return
                        from te in readContext.TaggedEntries
                        where te.ContextKey == contextId.ItemPath
                        group te by te.Tag into tags
                        orderby tags.Key
                        select new PropertyAggregate { Value = tags.Key, Count = tags.Count() };
                }
                else
                {
                    return
                      from te in readContext.TaggedEntries
                      where contextId.ToString() == te.ContextKey.Substring(0, contextId.ToString().Length)
                      group te by te.Tag into tags
                      orderby tags.Key
                      select new PropertyAggregate { Value = tags.Key, Count = tags.Count() };
                }
            }
            else if (string.Compare(propertyName, "datecreated", true) == 0)
            {
                if (contextMatch == MonikerMatch.Exact)
                {
                    return
                        from t in readContext.TextEntries
                        where t.ContextKey == contextId.ItemPath
                        group t by t.Created.Date
                            into dates
                            orderby dates.Key
                            select new PropertyAggregate { Value = dates.Key.ToString(), Count = dates.Count() };
                }
                else
                {
                    return
                     from t in readContext.TextEntries
                     where contextId.ToString() == t.ContextKey.Substring(0, contextId.ToString().Length)
                     group t by t.Created.Date
                         into dates
                         orderby dates.Key
                         select new PropertyAggregate { Value = dates.Key.ToString(), Count = dates.Count() };
                }
            }
            else
            {
                throw new ArgumentOutOfRangeException("propertyName",
                                                string.Format("Unsupported property '{0}'", propertyName));
            }
        }
    }
}
